package itecs;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import models.ExtendedCloudlet;
import models.SLA;

import org.cloudbus.cloudsim.Cloudlet;
import org.cloudbus.cloudsim.CloudletSchedulerSpaceShared;
import org.cloudbus.cloudsim.Datacenter;
import org.cloudbus.cloudsim.DatacenterCharacteristics;
import org.cloudbus.cloudsim.Host;
import org.cloudbus.cloudsim.Log;
import org.cloudbus.cloudsim.Pe;
import org.cloudbus.cloudsim.Storage;
import org.cloudbus.cloudsim.UtilizationModelFull;
import org.cloudbus.cloudsim.Vm;
import org.cloudbus.cloudsim.VmAllocationPolicySimple;
import org.cloudbus.cloudsim.VmSchedulerSpaceShared;
import org.cloudbus.cloudsim.core.CloudSim;
import org.cloudbus.cloudsim.provisioners.BwProvisionerSimple;
import org.cloudbus.cloudsim.provisioners.PeProvisionerSimple;
import org.cloudbus.cloudsim.provisioners.RamProvisionerSimple;

import simulation.Observation;
import util.Globals;
import util.MathUtils;
import util.ObservationComparator;
import brokers.FCFSBroker;

public class DedicatedItecs {


	private static double mu;
	private int hostidtracker=0;

	private int cloudletidtracker=0;
	private int vmidtracker=0;
	private int m1;
	private int m2;
	private String serviceDistribution;
	private SLA sla1;
	private SLA sla2;
	private double lambda1;
	private double lambda2;
	private FCFSBroker broker1;
	private FCFSBroker broker2;
	private ArrayList<Vm> vmList1= new ArrayList<Vm>();
	private ArrayList<Vm> vmList2=new ArrayList<Vm>();
	private UtilizationModelFull utilizationModel;
	private ArrayList<ExtendedCloudlet> cloudlet1List = new ArrayList<ExtendedCloudlet> ();
	private ArrayList<ExtendedCloudlet> cloudlet2List =new ArrayList<ExtendedCloudlet> ();
	private boolean SLA2IsMet;
	private boolean SLA1IsMet;
	private boolean SLAsIsMet;
	private long seed1;
	private long seed2;








	public DedicatedItecs(SLA sla1, SLA sla2, String serviceDistribution, double lambda1, double lambda2) throws Exception{

		this.serviceDistribution = serviceDistribution;
		this.sla1 = sla1;
		this.sla2 = sla2;
		this.lambda1 = lambda1;
		this.lambda2 = lambda2;
		DedicatedItecs.mu = 1/Globals.CLOUDLET_LENGTH_MEAN;
	}

	public void init(int numberOfServer1, int numberOfServer2) throws Exception{

		this.m1 = numberOfServer1;
		this.m2 = numberOfServer2;

		vmList1.clear();
		vmList2.clear();
		cloudlet1List.clear();
		cloudlet2List.clear();
		initTheCloud(numberOfServer1, numberOfServer2);

		System.out.println("\nINITIALIZING DEDICATED ITECS WITH " + this.m1+ " AND "+ this.m2 + " HOSTS and " + this.seed1 + " as SEEDS");
		//Itecs.writer = new FileWriter("output/output-FCFS-DA-" + m1+"-"+m2 + "-" + seed1  + "-" + lambda1 + "-" + lambda2+ "-" + mu/Globals.VM_MIPS  + ".csv", false);

		broker1 = new FCFSBroker("FCFSbroker1", Globals.SIMULATION_TIME,  sla1, sla1);
		broker2 = new FCFSBroker("FCFSbroker2", Globals.SIMULATION_TIME,  sla2, sla2);

		ArrayList<Double> serviceTimes1 = null;
		ArrayList<Double> serviceTimes2 = null;


		ArrayList<Double> arrivalTimes1 = MathUtils.getArrivals(this.generateSeed(), lambda1);
		ArrayList<Double> arrivalTimes2 = MathUtils.getArrivals(this.generateSeed(), lambda2);

		if (serviceDistribution.equals("EXP")) 
		{
			serviceTimes1 = MathUtils.getExponentialServiceTimes(this.generateSeed(), mu, arrivalTimes1.size());
			serviceTimes2 = MathUtils.getExponentialServiceTimes(this.generateSeed(), mu, arrivalTimes2.size());
			System.out.println("Service time distribution: EXPONENTIAL");
		}
		else if (serviceDistribution.equals("PARETO")) 
		{
			serviceTimes1 = MathUtils.getParetoServiceTimes(this.generateSeed(), Globals.PARETO_SHAPE, Globals.PARETO_LOCATION, arrivalTimes1.size()); //m:480, v: 9600
			serviceTimes2 = MathUtils.getParetoServiceTimes(this.generateSeed(), Globals.PARETO_SHAPE, Globals.PARETO_LOCATION, arrivalTimes2.size()); // m:480, v: 9600
			System.out.println("service time distribution: PARETO");
		}
		else 
		{
			serviceTimes1 = MathUtils.getExponentialServiceTimes(this.generateSeed(), mu, arrivalTimes1.size());
			serviceTimes2 = MathUtils.getExponentialServiceTimes(this.generateSeed(), mu, arrivalTimes2.size());
			System.out.println("Default service time distribution: EXPONENTIAL");
		}

		vmList1 = getVms(broker1.getId(), m1);
		vmList2 = getVms(broker2.getId(), m2);
		cloudlet1List = getDedicatedCloudlets(broker1.getId(),arrivalTimes1, serviceTimes1, 1 );
		cloudlet2List = getDedicatedCloudlets(broker2.getId(), arrivalTimes2, serviceTimes2, 2);

		broker1.submitVmList(vmList1);
		broker2.submitVmList(vmList2);
		broker1.submitCloudletList(cloudlet1List);
		broker2.submitCloudletList(cloudlet2List);

		//broker.printStatistics();
		//this.printCloudletList(cloudletList);

	}


	private ArrayList<Vm> getVms(int brokerId, int vmsNumber) {

		ArrayList<Vm> list = new ArrayList<Vm>(); 

		for (int i=0; i<vmsNumber; i++){
			Vm vm = new Vm(vmidtracker, brokerId, Globals.VM_MIPS, Globals.VM_CPU_NUMBER, Globals.VM_RAM, Globals.VM_BW, Globals.VM_SIZE, Globals.VM_NAME + "-" + i, new CloudletSchedulerSpaceShared());
			list.add(vm);
			vmidtracker++;
		}

		return list;
	}


	private ArrayList<ExtendedCloudlet> getDedicatedCloudlets(int brokerid, ArrayList<Double> arrivalTimes1, ArrayList<Double> serviceTimes1, int classid) {

		ArrayList<ExtendedCloudlet> list = new ArrayList<ExtendedCloudlet>();
		ExtendedCloudlet c;
		utilizationModel=new UtilizationModelFull();
		int id = cloudletidtracker;

		for (int i = 0; i < arrivalTimes1.size(); i++){
			c = createDedicatedCloudlet(id++, arrivalTimes1.get(i), serviceTimes1.get(i).longValue(), classid, brokerid );
			list.add(c);
			cloudletidtracker++;
		}
		return list;
	}

	private ExtendedCloudlet createDedicatedCloudlet(int id, Double arrivalTime, long serviceTime, int classId, int brokerid) {

		long cloudletLength =  serviceTime;

		ExtendedCloudlet c = new ExtendedCloudlet(id, cloudletLength, Globals.CPU_PER_CLOUDLET, Globals.CLOUDLET_FILE_SIZE, Globals.CLOUDLET_OUTPUT_SIZE, utilizationModel, utilizationModel, utilizationModel);
		c.setClassType(classId);
		c.setUserId(brokerid);
		c.setExecStartTime(arrivalTime.doubleValue());
		c.setActualSubmitTime(arrivalTime.doubleValue());

		return c;
	}


	private void initTheCloud(int hostNumber1, int hostNumber2) {
		CloudSim.init(1, Calendar.getInstance(), false);

		// DATACENTER CREATION
		this.createDatacenter(1, hostNumber1+hostNumber2);
	}


	/**
	 * Creates the datacenter.
	 *
	 * @param hostNumber the number of desired host
	 * in datacenter
	 *
	 * @return the datacenter
	 */
	private Datacenter createDatacenter(int id, int hostNumber) {

		List<Host> hostList = new ArrayList<Host>();

		int hostId = hostidtracker;
		for (int i = 0; i < hostNumber; i++) {
			List<Pe> peList = new ArrayList<Pe>();
			peList.add(new Pe(id, new PeProvisionerSimple(Globals.PE_MIPS)));
			//System.out.println("PE id: " + i + " added");
			//System.out.println("Host id: " + (hostId + i) + " added");
			hostList.add(new Host(hostId+i, new RamProvisionerSimple(Globals.HOST_RAM),
					new BwProvisionerSimple(Globals.HOST_BW), Globals.HOST_STORAGE, peList,
					new VmSchedulerSpaceShared(peList)));
			hostidtracker++;
		}

		//System.out.println("Host array size: " + hostList.size());
		LinkedList<Storage> storageList = new LinkedList<Storage>(); 

		DatacenterCharacteristics characteristics = new DatacenterCharacteristics(
				Globals.DATACENTER_ARCH, Globals.DATACENTER_OS, Globals.DATACENTER_VMM, hostList, Globals.DATACENTER_TIME_ZONE, 
				Globals.DATACENTER_COST_PER_CPU, Globals.DATACENTER_COST_PER_MEM, Globals.DATACENTER_COST_PER_STORAGE, Globals.DATACENTER_COST_PER_BW);

		Datacenter datacenter = null;
		try {
			datacenter = new Datacenter(Globals.DATACENTER_NAME+id, characteristics, new VmAllocationPolicySimple(hostList), storageList, 0);
			System.out.println(Globals.DATACENTER_NAME + " has id: " + datacenter.getId());
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("error in creating datacenter: aborting");
			System.exit(-1);
		}

		return datacenter;
	}

	public boolean SLAsIsMet() {
		return SLAsIsMet;
	}


	public void printDedicatedStats() {
		broker1.printStatistics();
		broker2.printStatistics();
		//printCloudletList(cloudletList);
	}


	public double getUtilization(){
		return broker1.getUtilization();
	}


	public void start(){

		CloudSim.startSimulation();
		CloudSim.stopSimulation();
		this.SLA1IsMet = broker1.SLAsAreMet();
		this.SLA2IsMet = broker2.SLAsAreMet();
		this.SLAsIsMet = broker1.SLAsAreMet() && broker2.SLAsAreMet();

	}

	public ArrayList<Observation> getObservations() {
		ArrayList<Observation> totalObservations = new ArrayList<Observation>();
		totalObservations.addAll(broker1.getObservations());
		totalObservations.addAll(broker2.getObservations());

		Collections.sort(totalObservations, new ObservationComparator());

		return totalObservations;
	}

	public void startDedicatedWithParams(long seed, int numberOfServer1, int numberOfServer2) throws Exception{

		this.seed1 = seed;
		this.seed2 = seed;
		this.m1 = numberOfServer1;
		this.m2 = numberOfServer2;
		DedicatedItecs.mu = 1/Globals.CLOUDLET_LENGTH_MEAN;

		System.out.println("\nINITIALIZING DEDICATED ITECS WITH " + this.m1+ " AND "+ this.m2 + " HOSTS and " + this.seed2 + " as SEEDS");
		//Itecs.writer = new FileWriter("output/output-FCFS-DA-" + m1+ "-" +m2 + "-" + seed1  +"-" + lambda1 + "-" + lambda2+ "-" + mu/Globals.VM_MIPS  + ".csv", false);

		vmList1.clear();
		vmList2.clear();
		cloudlet1List.clear();
		cloudlet2List.clear();
		initTheCloud(m1, m2);

		broker1 = new FCFSBroker("FCFSbroker1", Globals.SIMULATION_TIME,  sla1, sla1);
		broker2 = new FCFSBroker("FCFSbroker2", Globals.SIMULATION_TIME,  sla2, sla2);

		ArrayList<Double> serviceTimes1 = null;
		ArrayList<Double> serviceTimes2 = null;


		ArrayList<Double> arrivalTimes1 = MathUtils.getArrivals(this.generateSeed(), lambda1);
		ArrayList<Double> arrivalTimes2 = MathUtils.getArrivals(this.generateSeed(), lambda2);

		if (serviceDistribution.equals("EXP")) 
		{
			serviceTimes1 = MathUtils.getExponentialServiceTimes(this.generateSeed(), mu, arrivalTimes1.size());
			serviceTimes2 = MathUtils.getExponentialServiceTimes(this.generateSeed(), mu, arrivalTimes2.size());
		}
		else if (serviceDistribution.equals("PARETO")) 
		{
			serviceTimes1 = MathUtils.getParetoServiceTimes(this.generateSeed(), Globals.PARETO_SHAPE, Globals.PARETO_LOCATION, arrivalTimes1.size());
			serviceTimes2 = MathUtils.getParetoServiceTimes(this.generateSeed(), Globals.PARETO_SHAPE, Globals.PARETO_LOCATION, arrivalTimes2.size());
		}
		else 
		{
			serviceTimes1 = MathUtils.getExponentialServiceTimes(this.generateSeed(), mu, arrivalTimes1.size());
			serviceTimes2 = MathUtils.getExponentialServiceTimes(this.generateSeed(), mu, arrivalTimes2.size());
			System.out.println("Default service time distribution: EXPONENTIAL");
		}

		vmList1 = getVms(broker1.getId(), m1);
		vmList2 = getVms(broker2.getId(), m2);
		cloudlet1List = getDedicatedCloudlets(broker1.getId(),arrivalTimes1, serviceTimes1, 1 );
		cloudlet2List = getDedicatedCloudlets(broker2.getId(), arrivalTimes2, serviceTimes2, 2);

		broker1.submitVmList(vmList1);
		broker2.submitVmList(vmList2);
		broker1.submitCloudletList(cloudlet1List);
		broker2.submitCloudletList(cloudlet2List);

		CloudSim.startSimulation();
		CloudSim.stopSimulation();

		this.SLAsIsMet = broker1.SLAsAreMet() && broker2.SLAsAreMet();

	}


	public static int[] getM(String serviceDistribution, int startingM1,int startingM2, SLA sla1, SLA sla2, double lambda1, double lambda2, long seed) throws Exception{

		int m1 = startingM1;
		int m2 = startingM2;


		DedicatedItecs di = new DedicatedItecs(sla1, sla2, serviceDistribution, lambda1, lambda2);

		while (di.asToMet()) {	
			di = new DedicatedItecs(sla1, sla2, serviceDistribution, lambda1, lambda2);
			di.setSeed(seed);
			di.init(m1, m2);
			di.start();
			di.printStats();
			if (!di.SLA1IsMet){
				m1++;
			}
			if (!di.SLA2IsMet){
				m2++;
			}

		}

		if (di.SLAsIsMet()){
			int ret[] ={m1, m2};
			return ret;
		}
		else {
			return null;
		}



	}

	public static int[] getDedicatedOptimalM(String serviceDistribution, SLA sla1, SLA sla2, double lambda1, double lambda2) throws Exception {

		int mcorrect = 0;
		int m[] = null;
		int m1 = Globals.DEDICATED_STARTING_M;
		int m2 = Globals.DEDICATED_STARTING_M;
		int seedIndex = 0;
		DedicatedItecs di = new DedicatedItecs(sla1, sla2, serviceDistribution, lambda1, lambda2);


		while (mcorrect != 3){
			mcorrect = 0;

			m = DedicatedItecs.getM(serviceDistribution, m1, m2, sla1, sla2, lambda1, lambda2, Globals.SEEDS[seedIndex]);

			if (m==null && seedIndex == (Globals.SEEDS.length -1)){
				System.out.println("Problem: m is null with all seeds");
				return null;
			}

			if(m != null){
				mcorrect++;
				m1=m[0];
				m2=m[1];
			}

			for (int j=1; j<Globals.SEEDS.length; j++){

				di.startDedicatedWithParams(Globals.SEEDS[j], m1, m2);
				di.printStats();

				if (di.SLAsIsMet()){
					mcorrect++;
					if (mcorrect==3){
						break;
					}
				}
			}

			if(mcorrect != 3)
				seedIndex = (seedIndex+1) % Globals.SEEDS.length;

		}

		System.out.println("Finally m is: " + m1 + " and " + m2);
		return m;
	}

	private void setSeed(long seed) {
		this.seed1=seed;
		this.seed2=seed;

	}

	private boolean asToMet() {

		if (broker1 == null || broker2 == null) {
			return true;
		}
		return !SLAsIsMet && broker1.getMaxQueueSize() != 0 && broker2.getMaxQueueSize()!=0;

	}

	private void printStats() {
		broker1.printStatistics();
		broker2.printStatistics();
		//printCloudletList(cloudlet1List);
		//printCloudletList(cloudlet2List);

	}

	public long generateSeed()						
	{
		long a=1220703125; //moltiplicatore a=5^13
		Long m=new Long("2147483648"); //modulo m=2^31
		for(int i=0;i<20;i++)
			seed1=(long)((a*seed1)%(m.doubleValue()));		
		return seed1;
	}

	public double getRunThroughput() {
		return broker1.getRunThroughput();
	}


	@SuppressWarnings("unused")
	private static void printCloudletList(ArrayList<ExtendedCloudlet> cloudletList2) {
		int size = cloudletList2.size();
		Cloudlet cloudlet;

		String indent = "    ";
		String shortindent = "  ";
		Log.printLine();
		Log.printLine("========== OUTPUT ========== ( size: " + size + " cloudlets)");
		Log.printLine("Cloudlet ID" + indent + "STATUS" + indent
				+ "Data center ID" + indent + "VM ID" + indent + "Time" + indent
				+ "Start Time" + indent + "Finish Time");

		DecimalFormat dft = new DecimalFormat("####.00");
		for (int i = 0; i < size; i++) {
			cloudlet = cloudletList2.get(i);

			int dynsize = new Integer(cloudlet.getCloudletId()).toString().length();
			String dynintent = "";
			for(int j = 0; j < 12 - dynsize; j++)
				dynintent += " ";

			Log.print(shortindent + cloudlet.getCloudletId() + dynintent);

			if (cloudlet.getCloudletStatus() == Cloudlet.SUCCESS) {
				Log.print("SUCCESS");

				Log.printLine(indent + indent + cloudlet.getResourceId()
						+ indent + indent + indent + cloudlet.getVmId()
						+ indent + indent
						+ dft.format(cloudlet.getActualCPUTime()) + indent
						+ indent + dft.format(cloudlet.getExecStartTime())
						+ indent + indent
						+ dft.format(cloudlet.getFinishTime()));
			}
		}
	}


}